/**
 *
 * \file
 * Port receiver.
 *
 */

/*
 *
 * Copyright (c) 2010, Jesper Hansen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *      * Redistributions of source code must retain the above copyright notice, this
 *       list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holders nor the names of any contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 */


#include "lwip/debug.h"
#include "lwip/stats.h"
#include "lwip/tcp.h"

#include "utils/uartstdio.h"
#include "utils/ustdlib.h"

#include <string.h>
#include <stdlib.h>
#include "receiver.h"

#include "isendir.h"
#include "ir.h"


#define IDLE_TIMEOUT	30		// 60 seconds, as the poll is called every 2 seconds


//!
//! Receiver State Struct
//!
typedef struct receiver_state_t
{
	struct tcp_pcb 	*connection;		//!< Current TCP connection
	char			*out_data;			//!< Pointer to data to be sent
	uint16_t		out_len;			//!< Length of data to send
	uint16_t		out_left;			//!< Remaining data in output buffer

	char 			*in_data;			//!< Incoming data buffer
	uint16_t		in_len;				//!< Length of data in input buffer
	uint16_t		timeout;			//!< Counter for idle timeout
#define RECEIVER_FLAG_CLOSE_CONNECTION	(1<<0)	//!< Force a close on this connection
	uint16_t		flags;				//!< Flags
} receiver_state_t;


ir_data_t ir_data[NUM_IR_CHANNELS];



//*****************************************************************************
//!
//! Handle an error on a connection.
//!
//*****************************************************************************
static void conn_err(void *arg, err_t err)
{
	LWIP_UNUSED_ARG(err);
	mem_free(arg);
}

//*****************************************************************************
//!
//! Close an established connection.
//!
//*****************************************************************************
static void close_conn(struct tcp_pcb *pcb, receiver_state_t *hs)
{
printf("closing, hs = %08x\n",hs);

	tcp_arg(pcb, NULL);
	tcp_sent(pcb, NULL);
	tcp_recv(pcb, NULL);
	tcp_close(pcb);

printf("free in_data %08x\n",hs->in_data);
	if (hs->in_data)
		free(hs->in_data);
printf("free out_data %08x\n",hs->out_data);
	if (hs->out_data)
		free(hs->out_data);
printf("free hs %08x\n",hs);
	free(hs);
}

//*****************************************************************************
//!
//!
//!
//*****************************************************************************
static void send_data(receiver_state_t *hs)
{
	err_t err;
	u16_t len;
	char *p;

	//
	// set start point in output data
	//
	p = hs->out_data + hs->out_len - hs->out_left;

	//
	// set length of data to transmit
	//
	len = hs->out_left;

	//
	// limit to space in output buffer
	//
	if (len > tcp_sndbuf(hs->connection))
		len = tcp_sndbuf(hs->connection);

	//
	// send data, if a memory error occur, divide send size by two and retry
	//
	do {
		err = tcp_write(hs->connection, p, len, 0);
		if (err == ERR_MEM)
			len /= 2;
	} while (err == ERR_MEM && len > 1);

	//
	// if the data was enqued ok, adjust remaining length
	if (err == ERR_OK)
		hs->out_left -= len;

}



//*****************************************************************************
//!
//! Poll for more data.
//!
//*****************************************************************************
static err_t receiver_poll(void *arg, struct tcp_pcb *pcb)
{
	receiver_state_t *hs;

	hs = arg;

	//printf("Receiver Poll\n");

	if (hs == NULL)
	{
		tcp_abort(pcb);
		return ERR_ABRT;
	}
	else
	{
		if (hs->flags & RECEIVER_FLAG_CLOSE_CONNECTION)
		{
			printf("Forcing a close connection in receiver poll\n");
			close_conn(pcb, hs);
			hs->flags &= ~RECEIVER_FLAG_CLOSE_CONNECTION;
			return ERR_OK;
		}

		// timeout and close connection if nothing has been received for N seconds
		hs->timeout++;
		if (hs->timeout > IDLE_TIMEOUT)
		{
			printf("Timed out in receiver poll, closing connection\n");
			close_conn(pcb,hs);
		}
	}
	return ERR_OK;
}

/*-----------------------------------------------------------------------------------*/
static err_t receiver_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
{
	receiver_state_t *hs;

	LWIP_UNUSED_ARG(len);

	hs = arg;

	if (hs->out_left > 0)
	{
		send_data(hs);
	}
	else
	{
		if (hs->flags & RECEIVER_FLAG_CLOSE_CONNECTION)
		{
			printf("receiver_sent: forcing closing connection\n");
			close_conn(pcb, hs);
			hs->flags &= ~RECEIVER_FLAG_CLOSE_CONNECTION;
		}
	}
	return ERR_OK;
}




//*****************************************************************************
//!
//! Skip to the next parameter in a comma delimited string.
//! \param 	str		A pointer to a pointer to a string with the data
//!
//*****************************************************************************
char *nextparam(char **str)
{
	char *q, *pcurrent = *str;
	if ((q = strchr(*str,',')) != NULL)
	{
		*q = 0; 		// terminate at ,
		*str = q+1;
	}
	else
		*str = NULL;
	return pcurrent;
}




void send_reply(receiver_state_t *hs, char *reply)
{
	//
	// setup reply
	//
	if (hs->out_data)
		free(hs->out_data);

	hs->out_data = malloc(strlen(reply)+5);
	if (hs->out_data == NULL)
	{
		ERROR("Couldn't alloc mem\n");
		return;
	}

	strcpy(hs->out_data,reply);

	hs->out_len = hs->out_left = strlen(hs->out_data);

	printf("SENDBACK: %s\n",hs->out_data);

	if (hs->out_left > 0)
	{
		//
		// setup callback
		//
		tcp_sent(hs->connection, receiver_sent);

		//
		// write data into buffers
		//
		send_data(hs);
	}

}





void sendir_func(receiver_state_t *hs)
{
	char *q;
	char connector[4];
	uint8_t channel;
	ir_data_t *pid;
	int id;
	char *p;
	char temp[40];

	p = hs->in_data + strlen(hs->in_data) + 1;	// skip over the now zero-terminated command

	strcpy(connector, nextparam(&p));		// connector X:Y

	// module should always be one,
	// but we don't really care, and don't check

	channel = connector[2] - '1';	// get 0..2 value from Y above
	if (channel > 2)
	{
		ERROR("Invalid channel %d\n",channel);
		return;						// invalid channel, fail
	}

	//
	// if channel is busy, it's a no-go
	//
	if (ir_data[channel].ir_busy)
	{
		//send_reply(hs,)
		printf("-- Skipped a sendir command\n");
		return;
	}

	//! \todo need to add error responses




	//
	// set pointer to ir_data structure
	//
	pid = &ir_data[channel];

	id = atol( nextparam(&p) );	// id
	pid->ir_id = id;
	pid->ir_frequency = atol( nextparam(&p) );
	pid->ir_repeat = atoi( nextparam(&p) );
	pid->ir_offset = atoi( nextparam(&p) );

//debug
	printf("id     %d\n",pid->ir_id);
	printf("freq   %d\n",pid->ir_frequency);
	printf("repeat %d\n",pid->ir_repeat);
	printf("offset %d\n",pid->ir_offset);
//

	pid->ir_steps = 0;
	while ( (q = nextparam(&p)) != NULL)
	{
		pid->ir_table[pid->ir_steps] = atoi(q);
//debug
//		printf("%d ",pid->ir_table[pid->ir_steps]);
//
		pid->ir_steps++;
	}

//debug
	printf("\n");
	printf("steps %d\n\n",pid->ir_steps);
//
/*
	//
	// free the input buffer
	//
	free(hs->in_data);
*/

	printf("Sending data on channel %d\n",channel);

	ir_send(pid);


	//! \todo  we're NOT ready with the IR at this time!!!!!!!!!!  A callback is needed

	usprintf(temp,"completeir,%s,%d\r",connector,id);
	send_reply(hs,temp);

}


void stopir_func(receiver_state_t* hs)
{
	char connector[4];
	uint8_t channel;
	char *p;
	char temp[40];

	p = hs->in_data + strlen(hs->in_data) + 1;	// skip over the now zero-terminated command

	strcpy(connector, nextparam(&p));		// connector X:Y

	// module should always be one,
	// but we don't really care, and don't check

	channel = connector[2] - '1';	// get 0..2 value from Y above
	if (channel > 2)
	{
		ERROR("Invalid channel %d\n",channel);
		return;						// invalid channel, fail
	}

	ir_stop(channel);

	usprintf(temp,"stopir,%s\r",connector);
	send_reply(hs,temp);
}


void getversion_func(receiver_state_t* hs)
{
}






//typedef void (*cmd_func)(receiver_state_t* hs);

struct command_table_t
{
		char *cmd_name;
		void (*cmd_func)(receiver_state_t* hs);
} command_table[] = {
		{"sendir",		sendir_func},
		{"stopir",		stopir_func},
		{"getversion",	getversion_func},
		{NULL, 			NULL},
};



//*****************************************************************************
//!
//! Parse a string
//!
//! \param	hs	Pointer to the receiver state structure
//!
//*****************************************************************************
void receiver_cmd_parser(receiver_state_t *hs)
{
	char *p,*q;
	struct command_table_t *ct = command_table;

	//
	// point to input data
	//
	p = hs->in_data;

	//
	// get command
	//
	q = nextparam(&p);

	printf("cmdparser: cmd=%s\n",q);

	//
	// scan through command table to find handler function
	//
	while (ct->cmd_func != NULL)
	{
		if (strcmp(q,ct->cmd_name) == 0)
		{
			ct->cmd_func(hs);
			return;
		}
		ct++;
	}
	ERROR("Unknown command: %s\n",q);
}

//*****************************************************************************
//!
//! Receive data from a connection.
//!
//! \param	arg		User supplied argument when connection was established.
//! \param	pcb		The connection PCB
//! \param	p		First PBUF in chain with data
//! \param	err
//!	\return			Error status, ERR_OK if ok
//!
//*****************************************************************************
static err_t receiver_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
	receiver_state_t *hs;

	hs = arg;

	if (err == ERR_OK && p != NULL)
	{
		//
		// allocate memory for the input
		//
		if (hs->in_data)
			free(hs->in_data);
		hs->in_data = malloc(p->tot_len);
		if (hs->in_data == NULL)
		{
			ERROR("Couldn't alloc hs->in_data\n");
			return ERR_MEM;
		}

		//
		// clear length
		//
		hs->in_len = 0;

		//
		// Inform TCP that we have taken the data.
		//
		tcp_recved(pcb, p->tot_len);

		//
		// Reset the IDLE timeout
		//
		hs->timeout = 0;

		//
		// loop through pbuf chain and add data to input buffer
		//
		while (1)
		{
			struct pbuf *q;
			//
			// copy data from the pbuf into our input buffer
			//
			strncpy(&hs->in_data[hs->in_len], p->payload, p->len);

			//
			// increment length of data in buffer
			//
			hs->in_len += p->len;

			//
			// add a terminating zero at the end of the buffer
			//
			hs->in_data[hs->in_len] = 0;

			//
			// if this was the last buffer, we're done
			if (p->tot_len == p->len)
				break;

			//
			// step to the next buffer in the pbuf chain
			// and free the current one
			//
			q = p;
			p = p->next;
			pbuf_free(q);
		}

		printf("received (%d bytes):\n%s\n", hs->in_len, hs->in_data);


		//! \todo should differentiate between ports here !!


		//
		// call command parser
		//
		receiver_cmd_parser(hs);

		//
		// free the pbuf
		//
		pbuf_free(p);
	}


	if (err == ERR_OK && p == NULL)
	{
		printf("Remote host closed connection.\n");
		close_conn(pcb, hs);
	}

	return ERR_OK;
}



//*****************************************************************************
//!
//! Accept an incoming connection.
//!
//*****************************************************************************
static err_t receiver_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
	receiver_state_t *hs;

	LWIP_UNUSED_ARG(arg);
	LWIP_UNUSED_ARG(err);

	tcp_setprio(pcb, TCP_PRIO_MIN);

	//
	// Allocate memory for the connection state structure
	//
	hs = (receiver_state_t *) malloc( sizeof(receiver_state_t) );
	if (hs == NULL)
	{
		ERROR("receiver_accept: Out of memory\n");
		return ERR_MEM;
	}

	//
	// Initialize the structure.
	//
	memset(hs,0,sizeof(receiver_state_t));

	//
	// save this connection control block
	//
	hs->connection = pcb;


	printf("port %d: new connection from %d.%d.%d.%d\n", pcb->local_port,(pcb->remote_ip.addr >> 0) & 0xff, (pcb->remote_ip.addr >> 8) & 0xff, (pcb->remote_ip.addr >> 16) & 0xff, (pcb->remote_ip.addr >> 24) & 0xff);

	//
	// set the structure to be passed along to all callbacks
	//
	tcp_arg(pcb, hs);

	//
	// incoming data is sent to the receiver_recv function
	//
	tcp_recv(pcb, receiver_recv);

	//
	// direct any errors to the conn_err function
	//
	tcp_err(pcb, conn_err);

	//
	// call the receiver_poll function every 2 seconds
	//
	tcp_poll(pcb, receiver_poll, 4);

	return ERR_OK;
}


//*****************************************************************************
//!
//! Initialize receiver and open for listening.
//! The the ports, 4998, 4999 and 5000 are opened and listening starts
//! If a port get a connection, the \ref receiver_accept function will
//! be called.
//!
//*****************************************************************************
void receiver_init(void)
{
	struct tcp_pcb *pcb;

	pcb = tcp_new();
	tcp_bind(pcb, IP_ADDR_ANY, 4998);	// sendir and other commands
	pcb = tcp_listen(pcb);
	tcp_accept(pcb, receiver_accept);

	pcb = tcp_new();
	tcp_bind(pcb, IP_ADDR_ANY, 4999);	// serial port data
	pcb = tcp_listen(pcb);
	tcp_accept(pcb, receiver_accept);
}


